package com.wolfking.converter.util;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.wolfking.converter.transform.*;
import com.wolfking.converter.transform.base.*;
import lombok.extern.slf4j.Slf4j;
import org.reflections.ReflectionUtils;

import java.lang.reflect.Field;
import java.util.*;

@Slf4j
public class FieldConverterUtil {

    public static final Set<FieldTransform<?>> FIELD_TRANSFORMS = new HashSet<FieldTransform<?>>() {{
        add(new BigDecimalFieldTransform());
        add(new IntegerFieldTransform());
        add(new DoubleFieldTransform());
        add(new LongFieldTransform());
        add(new BooleanFieldTransform());
        add(new CharFieldTransform());
        add(new FloatFieldTransform());
        add(new DateFieldTransform());
        add(new BigIntegerFieldTransform());
        add(new StringFieldTransform());
        add(new ByteArrayFieldTransform());
        add(new ByteArray2FieldTransform());
        add(new CharArrayFieldTransform());
        add(new EnumFieldTransform());
    }};

    public static void registerTransform(FieldTransform<?> fieldTransform) {
        if (fieldTransform != null) {
            FIELD_TRANSFORMS.add(fieldTransform);
        }
    }

    public static void assemblyObject(Map<String, Object> map, Object obj) throws Exception {
        assemblyObject(map, obj, Maps.newHashMap());
    }

    public static void assemblyObject(Map<String, Object> map, Object obj, Map<String, FieldTransform<?>> fieldTransformMap) throws Exception {
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            String fieldName = entry.getKey();
            Object value = entry.getValue();
            setField(obj, fieldName, value, fieldTransformMap.get(fieldName));
        }
    }


    public static void assemblyObjectIgnoreException(Map<String, Object> map, Object obj) {
        assemblyObjectIgnoreException(map, obj, Maps.newHashMap());
    }


    public static void assemblyObjectIgnoreException(Map<String, Object> map, Object
            obj, Map<String, FieldTransform<?>> fieldTransformMap) {
        Class<?> clazz = obj.getClass();
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            String fieldName = entry.getKey();
            Object value = entry.getValue();
            try {
                setField(obj, fieldName, value, fieldTransformMap.get(fieldName));
            } catch (Exception e) {
                log.error("{} set filed {}={} error", clazz, fieldName, value);
            }
        }
    }


    public static <T> T assemblyObject(Map<String, Object> map, Class<T> clazz) throws Exception {
        return assemblyObject(map, clazz, Maps.newHashMap());
    }

    @SuppressWarnings("all")
    public static <T> T assemblyObject(Map<String, Object> map, Class<T> clazz, Map<String, FieldTransform<?>> fieldTransformMap) throws
            Exception {
        Object obj = clazz.newInstance();
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            String fieldName = entry.getKey();
            Object value = entry.getValue();
            setField(obj, fieldName, value, fieldTransformMap.get(fieldName));
        }
        return (T) obj;
    }


    public static <T> List<T> assemblyObjectList(List<Map<String, Object>> mapList, Class<T> clazz) throws
            Exception {
        return assemblyObjectList(mapList, clazz, Maps.newHashMap());
    }

    public static <
            T> List<T> assemblyObjectList(List<Map<String, Object>> mapList, Class<T> clazz, Map<String, FieldTransform<?>> fieldTransformMap) throws
            Exception {
        List<T> list = Lists.newArrayList();
        for (Map<String, Object> map : mapList) {
            list.add(assemblyObject(map, clazz, fieldTransformMap));
        }
        return list;
    }


    public static <T> T assemblyObjectIgnoreException(Map<String, Object> map, Class<T> clazz) {
        return assemblyObjectIgnoreException(map, clazz, Maps.newHashMap());
    }

    @SuppressWarnings("all")
    public static <T> T assemblyObjectIgnoreException(Map<String, Object> map, Class<T> clazz, Map<String, FieldTransform<?>> fieldTransformMap) {
        Object obj = null;
        try {
            obj = clazz.newInstance();
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                String fieldName = entry.getKey();
                Object value = entry.getValue();
                try {
                    setField(obj, fieldName, value, fieldTransformMap.get(fieldName));
                } catch (Exception e) {
                    log.warn("assemblyFieldIgnoreException {} set filed {}={} error", clazz, fieldName, value);
                }
            }
        } catch (Exception e) {
            log.error("{} set filed error", clazz);
        }
        return (T) obj;
    }


    public static <T> List<T> assemblyObjectListIgnoreException(List<Map<String, Object>> mapList, Class<T> clazz) {
        return assemblyObjectListIgnoreException(mapList, clazz, Maps.newHashMap());
    }

    public static <
            T> List<T> assemblyObjectListIgnoreException(List<Map<String, Object>> mapList, Class<T> clazz, Map<String, FieldTransform<?>> fieldTransformMap) {
        List<T> list = Lists.newArrayList();
        for (Map<String, Object> map : mapList) {
            list.add(assemblyObjectIgnoreException(map, clazz));
        }
        return list;
    }

    private static final Map<Class<?>, Set<Field>> CLASS_FIELD_SET_MAP = Maps.newHashMap();

    @SuppressWarnings("all")
    private static Field getField(Class<?> clazz, String name) {
        Set<Field> allFields;
        if (!CLASS_FIELD_SET_MAP.containsKey(clazz)) {

            allFields = ReflectionUtils.getAllFields(clazz);
            CLASS_FIELD_SET_MAP.put(clazz, allFields);
        } else {
            allFields = CLASS_FIELD_SET_MAP.get(clazz);
        }
        for (Field field : allFields) {
            if (field.getName().equals(name)) {
                return field;
            }
        }
        return null;
    }

    private static void setField(Object obj, String fieldName, Object value, FieldTransform<?> fieldTransformCustom) throws Exception {
        Class<?> clazz = obj.getClass();
        if (!fieldName.contains(".")) {
            if (value == null) {
                return;
            }
            Field field = getField(clazz, fieldName);
            if (field == null) {
                return;
            }
            field.setAccessible(true);
            Class<?> fieldType = field.getType();

            if (fieldTransformCustom != null) {
                field.set(obj, fieldTransformCustom.transform(fieldType, value));
            } else if (fieldType.isAssignableFrom(value.getClass())) {
                field.set(obj, value);
            } else {
                boolean set = false;
                Exception throwE = null;
                for (FieldTransform<?> fieldTransform : FIELD_TRANSFORMS) {
                    if (fieldTransform.canTransform(fieldType)) {
                        try {
                            Object transform = fieldTransform.transform(fieldType, value);
                            field.set(obj, transform);
                            set = true;
                            break;
                        } catch (Exception e) {
                            throwE = e;
                        }
                    }
                }
                if (!set) {
                    log.warn("transform filed {}={} error", fieldName, value);
                    if (throwE != null) {
                        throw throwE;
                    }
                }
            }
        } else {
            String[] split = fieldName.split("\\.");
            fieldName = split[0];
            Field field = getField(clazz, fieldName);
            if (field == null) {
                return;
            }
            field.setAccessible(true);
            Object o = field.get(obj);
            if (o == null) {
                o = field.getType().newInstance();
                field.set(obj, o);
            }
            fieldName = Arrays.stream(split).skip(1).reduce("", (e1, e2) -> (e1 + "." + e2)).substring(1);
            setField(o, fieldName, value, fieldTransformCustom);
        }
    }
}
